/**
 * 
 * @Project HRL Simulation
 * @Date 20.06.2011
 * @Author Aebi Pascal, Kocher Marc, Sriranjan George
 * @School BFH-TI Biel, Projekt 1
 *
 */

package strategy.strategies;

import java.util.ArrayList;
import java.util.List;

import strategy.AbstractStrategy;
import utils.AgendaEntry;
import utils.Position;
import utils.Resource;
import elements.Buffer;
import elements.Conveyor;
import elements.Lift;
import elements.Load;
import elements.Rack;
import exceptions.ResourceException;


public class OptimizedStrategy extends AbstractStrategy {

	
	/** The constants for the different conflict IDs. */
	private final int ENTRY_BUFFER_FAILURE_ID = 0;
	private final int CONVEYOR_FAILURE_ID = 1;
	private final int INPUT_BUFFER_FAILURE_ID = 2;
	private final int OUTPUT_BUFFER_FAILURE_ID = 3;
	private final int LIFT_FAILURE_ID = 4;
	/** The estimated conveyor time. */
	private int estimatedConveyorTime;
	
	
	/**
	 * Creates an optimized strategy
	 * 
	 * @param racks
	 * @param buffers
	 * @param lifts
	 * @param conveyor
	 */
	public OptimizedStrategy(List<Rack> racks, List<Buffer> buffers, List<Lift> lifts, Conveyor conveyor){
		this.racks = racks;
		this.buffers = buffers;
		this.lifts = lifts;
		this.conveyor = conveyor;
		// Get entry buffer
		this.entryBuffer = this.buffers.get(0);
		// Get exit buffer
		this.exitBuffer = this.buffers.get(buffers.size()-1);
		// Calculate max conveyor route
		this.maxConveyorRoute = 2*(this.RACK_LENGTH + 4) + 2*(this.racks.size() + this.lifts.size());
	}
	
	
	/**
	 * Books all resources for a given load which has to store somewhere in a rack
	 * 
	 * @param load - load to book
	 * @throws ResourceException calculate route error
	 */
	@Override
	public void bookLoadIn(Load load) throws ResourceException {
		// Set load
		this.load = load;
		
		// Set system entry time
		this.systemEntryTime = load.getEntryTime();
		
		boolean hasFreeRackResource = false;
		for (Rack r : this.racks) {
			List<Resource> res = r.getFreeResources(this.systemEntryTime);
			if(!res.isEmpty()){
				hasFreeRackResource = true;
				break;
			}
		}
		
		if(hasFreeRackResource){
			// Initialize variables
			int route = 0;
			int startTime = 0;
			int endTime = 0;
		
			/** Select needed input resources **/
			this.selectInputResources();
			
			/** Book entry buffer **/
			route = this.calculateRouteFromTo(entryBuffer, entryBuffer);
			startTime = this.systemEntryTime;
			endTime = startTime + route*this.CONVEYOR_SPEED;
			AgendaEntry entryBufferAE = new AgendaEntry(startTime, this.entryBuffer.getPosition(), endTime, this.entryBuffer.getPosition(), load);
			this.entryBuffer.getAgenda().addEntry(entryBufferAE);
			System.out.println(entryBufferAE + "  |  " + this.entryBuffer);
			
			/** Book conveyor resources **/
			int inputBufferX = this.inputBuffer.getPosition().getX();
			int inputBufferY = this.inputBuffer.getPosition().getY();
			for (Resource conveyorResource : this.availableConveyorResources) {	
				int conveyorResourceX = conveyorResource.getPosition().getX();
				int conveyorResourceY = conveyorResource.getPosition().getY();
				// Calculate start time
				route = this.calculateRouteFromTo(this.entryBuffer, conveyorResource);
				startTime = this.systemEntryTime + route*this.CONVEYOR_SPEED;
				// Calculate end time
				route = this.calculateRouteFromTo(conveyorResource, conveyorResource);
				endTime = startTime + route*this.CONVEYOR_SPEED;
				// Create agenda entry
				AgendaEntry newAE = new AgendaEntry(startTime, conveyorResource.getPosition(), endTime, conveyorResource.getPosition(), load);
				if(inputBufferY == 1){
					if(conveyorResourceX < inputBufferX && conveyorResourceY < (4+this.RACK_LENGTH)/2+1){
						System.out.println(newAE);
						conveyorResource.getAgenda().addEntry(newAE);
					}
				}else if (inputBufferY > (4+this.RACK_LENGTH)/2) {
					if (conveyorResourceY > (4+this.RACK_LENGTH)/2 && conveyorResourceX < inputBufferX){
						// do nothing
					}else{
						System.out.println(newAE);
						conveyorResource.getAgenda().addEntry(newAE);
					}
				}
			}
			
			/** Book lift to input buffer **/
			Position lastStartPos = null;
			// Get last lift agenda entry
			AgendaEntry lastLiftAE = this.lift.getAgenda().getLastEntry();
			Position upperLiftPos = new Position(inputBufferX+1, inputBufferY, this.inputBuffer.getPosition().getZ());
			Position lowerLiftPos = new Position(inputBufferX-1, inputBufferY, this.inputBuffer.getPosition().getZ());
			Position newLiftPos = null;
			// First time null, so use default lift position
			if(lastLiftAE.getEndPos() == null){
				lastStartPos = this.lift.getPosition();
			}else{
				lastStartPos = lastLiftAE.getEndPos();
			}
			// Check if the input buffer is above or underneath of the lift
			if(lastStartPos.getX() == this.inputBuffer.getPosition().getX()+1){
				newLiftPos = upperLiftPos;
			}else{
				newLiftPos = lowerLiftPos;
			}
			// Calculate lift route
			route = this.calculateRouteFromTo(this.lift, this.inputBuffer);
			int lastLiftEndTime = this.lift.getAgenda().getLastEntry().getEndTime();
			if(lastLiftEndTime > this.systemEntryTime){
				startTime = lastLiftEndTime;
			} else {
				startTime = this.systemEntryTime;
			}
			startTime = this.lift.getAgenda().getLastEntry().getEndTime();
			endTime = startTime + route*this.LIFT_SPEED;
			if(route > 0){
				AgendaEntry liftAE = new AgendaEntry(startTime, lastStartPos, endTime, newLiftPos, null);
				lift.getAgenda().addEntry(liftAE);
				System.out.println(liftAE + "  |  " + lift);
			}
			
			/** Book input buffer (and lift if needed -> waiting) **/
			route = this.calculateRouteFromTo(this.entryBuffer, this.inputBuffer);
			startTime = this.systemEntryTime + route*this.CONVEYOR_SPEED;
			lastLiftEndTime = this.lift.getAgenda().getLastEntry().getEndTime();
			if(lastLiftEndTime > startTime){
				endTime = lastLiftEndTime;
			} else {
				route = this.calculateRouteFromTo(this.inputBuffer, this.lift);
				endTime = startTime + route*this.CONVEYOR_SPEED;
				
				Position liftWaitingPosition = this.lift.getAgenda().getLastEntry().getEndPos();
				if(liftWaitingPosition == null){
					liftWaitingPosition = this.lift.getPosition();
				}
				
				// Add lift agenda entry if lift has to wait on load
				AgendaEntry liftWaitingAE = new AgendaEntry(lastLiftEndTime, liftWaitingPosition, endTime, liftWaitingPosition, null);
				this.lift.getAgenda().addEntry(liftWaitingAE);
				System.out.println(liftWaitingAE + "  |  " + lift);
			}
			AgendaEntry inputBufferAE = new AgendaEntry(startTime, this.inputBuffer.getPosition(), endTime , this.inputBuffer.getPosition(), load);
			this.inputBuffer.getAgenda().addEntry(inputBufferAE);
			System.out.println(inputBufferAE + "  |  " + this.inputBuffer);
			
			/** Book loading load **/
			if(lastLiftEndTime > startTime){
				// Book input buffer for loading load
				route = this.calculateRouteFromTo(this.inputBuffer, this.inputBuffer);
				startTime = this.inputBuffer.getAgenda().getLastEntry().getEndTime();
				endTime = startTime + route*this.CONVEYOR_SPEED;
				AgendaEntry loadingAE = new AgendaEntry(startTime, this.inputBuffer.getPosition(), endTime, this.inputBuffer.getPosition(), load);
				this.inputBuffer.getAgenda().addEntry(loadingAE);
				System.out.println(loadingAE + "  |  loading entry  |  " + this.inputBuffer);
				
				// Book lift for loading load
				route = this.calculateRouteFromTo(this.inputBuffer, this.lift);
				startTime = this.lift.getAgenda().getLastEntry().getEndTime();
				endTime = startTime + route*this.CONVEYOR_SPEED;
				AgendaEntry toLiftAE = new AgendaEntry(startTime, this.lift.getAgenda().getLastEntry().getEndPos(), endTime, this.lift.getAgenda().getLastEntry().getEndPos(), null);
				this.lift.getAgenda().addEntry(toLiftAE);
				System.out.println(toLiftAE + "  |  loading entry  |  " + this.lift);
			}
			
			/** Book lift transport load **/
			if(this.lift.getPosition().getX() == this.rackResource.getPosition().getX()+1){
				newLiftPos = new Position(this.rackResource.getPosition().getX()+1, this.rackResource.getPosition().getY(), rackResource.getPosition().getZ());
			}else{
				newLiftPos = new Position(this.rackResource.getPosition().getX()-1, this.rackResource.getPosition().getY(), rackResource.getPosition().getZ());
			}
			route = this.calculateRouteFromTo(this.lift, this.rackResource);
			startTime = this.lift.getAgenda().getLastEntry().getEndTime();
			endTime = startTime + route*this.LIFT_SPEED;
			AgendaEntry transportLiftAE = new AgendaEntry(startTime, this.lift.getAgenda().getLastEntry().getEndPos(), endTime, newLiftPos, load);
			this.lift.getAgenda().addEntry(transportLiftAE);
			System.out.println(transportLiftAE + "  |  " + this.lift);
			
			/** Book lift unloading load **/
			route = this.calculateRouteFromTo(this.lift, rackResource);
			startTime = this.lift.getAgenda().getLastEntry().getEndTime();
			endTime = startTime + route*this.CONVEYOR_SPEED;
			AgendaEntry fromLiftAE = new AgendaEntry(startTime, this.lift.getAgenda().getLastEntry().getEndPos(), endTime, this.lift.getAgenda().getLastEntry().getEndPos(), load);
			this.lift.getAgenda().addEntry(fromLiftAE);
			System.out.println(fromLiftAE + "  |  " + this.lift);
			
			/** Book rack resource **/
			startTime = this.lift.getAgenda().getLastEntry().getEndTime();
			endTime = Integer.MAX_VALUE-1;
			AgendaEntry rackAE = new AgendaEntry(startTime, this.rackResource.getPosition(), endTime, this.rackResource.getPosition(), load);
			this.rackResource.getAgenda().addEntry(rackAE);
			System.out.println(rackAE + "  |  " + this.rackResource);
		}
		else{
			throw new ResourceException("All racks are full");
		}
	}

	
	/**
	 * Books all resources for a given load which has to go out of the system
	 * 
	 * @param load - load to book
	 * @throws ResourceException Position not found
	 */
	@Override
	public void bookLoadOut(Load load) throws ResourceException {
		int startTime = 0;
		int route = 0;
		int endTime = 0;
		this.systemEntryTime = load.getEntryTime();
		System.out.println("system entery time: " + this.systemEntryTime);
		Position liftEndPosition;
		Position liftStartPosition;
		this.rackResource = this.getRackResourceOfLoad(load);
		// Set load
		this.load = load;
		
		// If a load is stored on given rack resource, book it out
		if(this.rackResource != null){
			
			/** Select the output buffer **/
			this.selectOutputResources();
			
			/** Book lift to rack **/
			// Calculate route and times to move the lift from its position to the rack resource
			route = this.calculateRouteFromTo(this.lift, this.rackResource);
			if(route > 0){
				startTime = this.lift.getAgenda().getLastEntry().getEndTime();
				endTime = startTime + route*this.LIFT_SPEED;
				// Calculate start and end position
				liftEndPosition = new Position(this.lift.getPosition().getX(), this.rackResource.getPosition().getY(), this.rackResource.getPosition().getZ());
				liftStartPosition = this.lift.getAgenda().getLastEntry().getEndPos() ;
				// Get initial lift position if position of last agenda entry is null
				if(liftStartPosition == null){
					liftStartPosition = this.lift.getPosition();
				}
				// Create agenda entry
				AgendaEntry liftToRackAE = new AgendaEntry(startTime, liftStartPosition, endTime, liftEndPosition, null);
				this.lift.getAgenda().addEntry(liftToRackAE);
				System.out.println(liftToRackAE + "  |  move lift to rack  |  " + this.lift);
			}
			
			/** Book lift (if needed to wait) **/
			AgendaEntry lastLiftAE = this.lift.getAgenda().getLastEntry();
			startTime = lastLiftAE.getEndTime();
			liftEndPosition = lastLiftAE.getEndPos();
			// First time null, so use default lift position
			if(liftEndPosition == null){
				liftEndPosition = this.lift.getPosition();
			}
			if(startTime < this.systemEntryTime){
				// Calculate route and positions
				route = this.calculateRouteFromTo(this.lift, this.lift);
				endTime = this.systemEntryTime;
				// Create agenda entry
				AgendaEntry liftWaitingAE = new AgendaEntry(startTime, liftEndPosition, endTime, liftEndPosition, null);
				this.lift.getAgenda().addEntry(liftWaitingAE);
				System.out.println(liftWaitingAE + "  |  wait on load  |  " + this.lift);
			}
			
			/** Book rack (release rack resource inclusive waiting time for lift) **/
			// Release rack resource, set end time (infinite entry) to correct time (last lift entry)
			for (Rack rack : this.racks) {
				if(rack.contains(this.rackResource)){
					rack.releaseResource(this.rackResource, this.lift.getAgenda().getLastEntry().getEndTime());
					System.out.println(this.rackResource.getAgenda().getLastEntry() + "  |  book rack (modified rack AE)");
					break;
				}
			}
			
			/** Book rack (loading lift) **/
			// Calculate route and times to move the load on the lift, if the lift is next to the rack resource
			route = this.calculateRouteFromTo(this.rackResource, this.lift);
			startTime = this.rackResource.getAgenda().getLastEntry().getEndTime();
			endTime = startTime + route*this.CONVEYOR_SPEED;
			Position rackResourcePosition = this.rackResource.getPosition();
			// Create agenda entry
			AgendaEntry rackToLiftAE = new AgendaEntry(startTime, rackResourcePosition, endTime, rackResourcePosition, load);
			this.rackResource.getAgenda().addEntry(rackToLiftAE);
			System.out.println(rackToLiftAE + "  |  load lift  |  " + this.rackResource);
			
			/** Book lift (loading) **/
			// Calculate route and times to move the load on the lift, if the lift is next to the rack resource
			route = this.calculateRouteFromTo(this.rackResource, this.lift);
			startTime = this.lift.getAgenda().getLastEntry().getEndTime();
			endTime = startTime + route*this.CONVEYOR_SPEED;
			liftStartPosition = this.lift.getAgenda().getLastEntry().getEndPos() ;
			// Get initial lift position if position of last agenda entry is null
			if(liftStartPosition == null){
				liftStartPosition = this.lift.getPosition();
			}
			// Create agenda entry
			AgendaEntry loadingLiftAE = new AgendaEntry(startTime, liftStartPosition, endTime, liftStartPosition, null);
			this.lift.getAgenda().addEntry(loadingLiftAE);
			System.out.println(loadingLiftAE + "  |  loading entry  |  " + this.lift);
			
			/** Book lift to buffer **/
			// Calculate route and times to move the lift (next to the rack resource) to the output buffer
			route = this.calculateRouteFromTo(this.lift, this.outputBuffer);
			startTime = this.lift.getAgenda().getLastEntry().getEndTime();
			endTime = startTime + route*this.LIFT_SPEED;
			// Calculate start and end position
			liftStartPosition = this.lift.getAgenda().getLastEntry().getEndPos() ;
			// Get initial lift position if position of last agenda entry is null
			if(liftStartPosition == null){
				liftStartPosition = this.lift.getPosition();
			}
			liftEndPosition = new Position(this.lift.getPosition().getX(), this.outputBuffer.getPosition().getY(), this.outputBuffer.getPosition().getZ());
			// Create agenda entry
			AgendaEntry liftToOutputBufferAE = new AgendaEntry(startTime, liftStartPosition, endTime, liftEndPosition, load);
			this.lift.getAgenda().addEntry(liftToOutputBufferAE);
			System.out.println(liftToOutputBufferAE + "  |  move lift to buffer  |  " + this.lift);
			
			/** Book unloading lift **/
			// Calculate route and times to move the load from the lift (next to the output buffer) to the output buffer
			route = this.calculateRouteFromTo(this.lift, this.outputBuffer);
			startTime = this.lift.getAgenda().getLastEntry().getEndTime();
			endTime = startTime + route*this.CONVEYOR_SPEED;
			// Calculate start and end position
			liftEndPosition = this.lift.getAgenda().getLastEntry().getEndPos();
			// First time null, so use default lift position
			if(liftEndPosition == null){
				liftEndPosition = this.lift.getPosition();
			}
			// Create agenda entry
			AgendaEntry liftUnloadingAE = new AgendaEntry(startTime, liftEndPosition, endTime, liftEndPosition, load);
			this.lift.getAgenda().addEntry(liftUnloadingAE);
			System.out.println(liftUnloadingAE + "  |  unload lift  |  " + this.lift);
			
			/** Book output buffer (waiting) **/
			startTime = this.lift.getAgenda().getLastEntry().getEndTime() + 1*this.CONVEYOR_SPEED;
			if(startTime<this.estimatedConveyorTime){
				// Calculate route and times for the output buffer
				startTime = this.lift.getAgenda().getLastEntry().getEndTime();
				endTime = this.estimatedConveyorTime;
				//reset time
				this.estimatedConveyorTime = 0;
				// Calculate start and end position
				Position resourcePosition = this.outputBuffer.getPosition();
				// Create agenda entry
				AgendaEntry outputBufferAE = new AgendaEntry(startTime, resourcePosition, endTime, resourcePosition, load);
				this.outputBuffer.getAgenda().addEntry(outputBufferAE);
				System.out.println(outputBufferAE + "  |  waiting  |  " + this.outputBuffer);
				
				// Unloading entry
				route = this.calculateRouteFromTo(this.outputBuffer, this.outputBuffer);
				startTime = this.outputBuffer.getAgenda().getLastEntry().getEndTime();
				endTime = startTime + route*this.CONVEYOR_SPEED;
				AgendaEntry unloadingBufferAE = new AgendaEntry(startTime, resourcePosition, endTime, resourcePosition, load);
				this.outputBuffer.getAgenda().addEntry(unloadingBufferAE);
				System.out.println(unloadingBufferAE + "  |  unloading buffer  |  " + this.outputBuffer);
			}
			else{
				// Calculate route and times to move the load from the lift (next to the output buffer) to the output buffer
				route = this.calculateRouteFromTo(this.lift, this.outputBuffer);
				startTime = this.lift.getAgenda().getLastEntry().getEndTime();
				endTime = startTime + route*this.CONVEYOR_SPEED;
				// Calculate start and end position
				Position ouputBufferPosition = this.outputBuffer.getPosition();
				// Create agenda entry
				AgendaEntry bufferLoadingAE = new AgendaEntry(startTime, ouputBufferPosition, endTime, ouputBufferPosition, load);
				this.outputBuffer.getAgenda().addEntry(bufferLoadingAE);
				System.out.println(bufferLoadingAE + "  |  book buffer  |  " + this.outputBuffer);
			}
			
			/** Book available conveyor resources **/
			for (Resource res : this.availableConveyorResources) {
				// Get positions
				int outputBufferX = this.outputBuffer.getPosition().getX();
				int outputBufferY = this.outputBuffer.getPosition().getY();
				int conveyorResourceX = res.getPosition().getX();
				int conveyorResourceY = res.getPosition().getY();
				// Calculate route and times for the output buffer
				route = this.calculateRouteFromTo(this.outputBuffer, res);
				
				if(Math.abs(conveyorResourceX-outputBufferX) == 0 && Math.abs(conveyorResourceY-outputBufferY) == 1){
					startTime = this.outputBuffer.getAgenda().getLastEntry().getEndTime();
				}
				else{
					startTime = this.outputBuffer.getAgenda().getLastEntry().getStartTime() + route*this.CONVEYOR_SPEED;
				}
				route = this.calculateRouteFromTo(res, res);
				endTime = startTime + route*this.CONVEYOR_SPEED;
				// Calculate start and end position
				Position resourcePosition = res.getPosition();
				// Create agenda entry
				AgendaEntry conveyorResourceAE = new AgendaEntry(startTime, resourcePosition, endTime, resourcePosition, load);
				res.getAgenda().addEntry(conveyorResourceAE);
				System.out.println(conveyorResourceAE + "  |  " + res);
			}
			
			/** Book exit buffer **/
			route = this.calculateRouteFromTo(this.outputBuffer, this.exitBuffer);
			startTime = this.outputBuffer.getAgenda().getLastEntry().getStartTime() + route*this.CONVEYOR_SPEED;
			route = this.calculateRouteFromTo(this.exitBuffer, this.exitBuffer);
			endTime = startTime + route*this.CONVEYOR_SPEED;
			// Calculate start and end position
			Position exitBufferPosition = this.exitBuffer.getPosition();
			// Create agenda entry
			AgendaEntry exitBufferAE = new AgendaEntry(startTime, exitBufferPosition, endTime, exitBufferPosition, load);
			this.exitBuffer.getAgenda().addEntry(exitBufferAE);
			System.out.println(exitBufferAE + "  |  " + this.exitBuffer);
			
		}
		// If no load is stored on given rack resource, throw an error
		else{
			System.out.println("this load is not in racks");
		}
	}

	
	/**
	 * Handles conflicts while the in booking process
	 * @throws ResourceException calculate route error
	 */
	@Override
	public void handleBookInConflict(int failureID) throws ResourceException {
		int waitingTime = 0;
		switch(failureID){
			case ENTRY_BUFFER_FAILURE_ID:
				int lastEntryBufferEndTime = this.entryBuffer.getAgenda().getLastEntry().getEndTime();
				waitingTime = lastEntryBufferEndTime-this.systemEntryTime;
				this.systemEntryTime += (waitingTime + 1*this.CONVEYOR_SPEED);
				break;
		
			case CONVEYOR_FAILURE_ID:
				this.systemEntryTime += 1*this.CONVEYOR_SPEED;
				System.out.println("conveyor failure occured! system entry time: " + this.systemEntryTime);
				break;
	
			case INPUT_BUFFER_FAILURE_ID:
				//wait on input buffer
				int lastBufferEndTime = this.inputBuffer.getAgenda().getLastEntry().getEndTime();
				int route = this.calculateRouteFromTo(this.entryBuffer, this.inputBuffer);
				int startTime = this.systemEntryTime + route*this.CONVEYOR_SPEED;
				waitingTime = lastBufferEndTime - startTime;
				this.systemEntryTime += waitingTime + 1*this.CONVEYOR_SPEED;
				break;
		}
	}
	
	
	/**
	 * Handles conflicts while the out booking process
	 * @throws ResourceException calculate route error
	 */
	@Override
	public void handleBookOutConflict(int failureID) throws ResourceException {
		
		int route = 0;
		int waitingTime = 0;
		
		switch(failureID){
			case LIFT_FAILURE_ID:
				// Calculate route to move the lift from its last position to the rack resource
				route = this.calculateRouteFromTo(this.lift, this.rackResource);
				int lastLiftEndTime = this.lift.getAgenda().getLastEntry().getEndTime();
				int liftEndTime = lastLiftEndTime + route*this.LIFT_SPEED;
				this.systemEntryTime = liftEndTime;
				System.out.println("lift failure occured! system entry time: " + this.systemEntryTime);
				break;
				
			case OUTPUT_BUFFER_FAILURE_ID:
				// Calculate time to move the load on the lift, if the lift is next to the rack resource
				route = 1;
				waitingTime = this.systemEntryTime + route*this.CONVEYOR_SPEED;
				
				// Calculate time to move the lift to the output buffer
				route = this.calculateRouteFromTo(this.rackResource, this.outputBuffer);
				waitingTime = waitingTime + route*this.LIFT_SPEED;
				
				route = 1;
				waitingTime = waitingTime + route*this.CONVEYOR_SPEED;

				int lastBufferEndTime = this.outputBuffer.getAgenda().getLastEntry().getEndTime();
				lastBufferEndTime += 1*this.CONVEYOR_SPEED;
				
				waitingTime = (lastBufferEndTime - waitingTime);
				this.systemEntryTime += waitingTime;
				System.out.println("buffer failure occured! system entry time: " + this.systemEntryTime);
				break;
				
			case CONVEYOR_FAILURE_ID:
				this.estimatedConveyorTime += 1*this.CONVEYOR_SPEED;
				System.out.println("conveyor failure till: " + this.estimatedConveyorTime);
				break;
		}
	}
	
	/**
	 * Selects an input buffer and a lift corresponding to the strategy
	 * 
	 * @throws ResourceException calculate route error
	 */
	private void selectInputResources() throws ResourceException {
		List<Resource> availableRackResources = new ArrayList<Resource>();
		
		for(Rack r : this.racks){
			availableRackResources.addAll(r.getAllResources());
		}
		
		int minRouteTime = 2147483646;
		
		for(Resource res : availableRackResources){
			
			List<Buffer> correspondingInputBuffers = new ArrayList<Buffer>();
			Buffer choosenInputBuffer = null;
			Lift choosenLift = null;
			
			// Select lift
			int rackResourceX = res.getPosition().getX();
			for(Lift lift : this.lifts){
				int liftX = lift.getPosition().getX();
				if(Math.abs(liftX - rackResourceX) == 1){
					choosenLift = lift;
					break;
				}	
			}
			
			// Search corresponding input buffers
			int liftX = choosenLift.getPosition().getX();
				for(Buffer buffer : this.buffers){
					int inputBufferX = buffer.getPosition().getX();
					if(Math.abs(liftX - inputBufferX) == 1 && buffer.getType() == Buffer.Type.IN){
					correspondingInputBuffers.add(buffer);
				}
			}
			
			int interimTime;
			// Entry buffer to input buffer
			Buffer inputBuffer1 = correspondingInputBuffers.get(0);
			Buffer inputBuffer2 = correspondingInputBuffers.get(1); 
			
			int routeTimeEntryBufferInputBuffer1 = this.calculateRouteFromTo(this.entryBuffer, inputBuffer1);
			int routeTimeEntryBufferInputBuffer2 = this.calculateRouteFromTo(this.entryBuffer, inputBuffer2);
			
			int routeTimeEntryBufferCchoosenInputBuffer = 0;
			if(routeTimeEntryBufferInputBuffer1 < routeTimeEntryBufferInputBuffer2){
				choosenInputBuffer = inputBuffer1;
				routeTimeEntryBufferCchoosenInputBuffer = routeTimeEntryBufferInputBuffer1;
			}
			else{
				choosenInputBuffer = inputBuffer2;
				routeTimeEntryBufferCchoosenInputBuffer = routeTimeEntryBufferInputBuffer2;
			}
			interimTime = routeTimeEntryBufferCchoosenInputBuffer+choosenInputBuffer.getAgenda().getLastEntry().getEndTime();

			// Move lift to input buffer
			AgendaEntry lastLiftAE = choosenLift.getAgenda().getLastEntry();
			Position liftStartPosition = lastLiftAE.getEndPos();
			if(liftStartPosition == null){
				liftStartPosition = choosenLift.getPosition();
			}
			
			int liftEndPositionY = liftStartPosition.getY();
			int inputBufferPositionY = choosenInputBuffer.getPosition().getY();
			int liftToInputBufferTime = Math.abs(liftEndPositionY-inputBufferPositionY);
			// set interimTime to the time when lift is arriving at input buffer (only if bigger --> waiting time)
			if(interimTime < liftToInputBufferTime+lastLiftAE.getStartTime()){
				interimTime = liftToInputBufferTime+lastLiftAE.getStartTime();
			}
			
			// Input buffer to lift
			int inputBufferToLift = 1*this.CONVEYOR_SPEED;
			interimTime = interimTime+inputBufferToLift;
			
			// Lift to rack resource
			int routeTimeInputBufferRes = Math.abs(choosenInputBuffer.getPosition().getY()-res.getPosition().getY())+res.getPosition().getZ()+2;
			interimTime = interimTime + routeTimeInputBufferRes;
			
			int lastEntry = res.getAgenda().getLastEntry().getEndTime();
			if(interimTime < minRouteTime){
				if(lastEntry <= this.systemEntryTime + interimTime){
					this.rackResource = res;
					this.lift = choosenLift;
					this.inputBuffer = choosenInputBuffer;
					minRouteTime = interimTime;
				}
			}
		}
		
		int lastEntryBufferEndTime = this.entryBuffer.getAgenda().getLastEntry().getEndTime();
		
		// Handle conflict
		if(this.systemEntryTime <= lastEntryBufferEndTime){
			this.handleBookInConflict(this.ENTRY_BUFFER_FAILURE_ID);
		}

		// Handle conflict
		while(this.hasBookInConveyorConflict(this.inputBuffer)){
			this.handleBookInConflict(this.CONVEYOR_FAILURE_ID);
		}
		
		int route = this.calculateRouteFromTo(this.entryBuffer, this.inputBuffer);
		int startTime = this.systemEntryTime + route*this.CONVEYOR_SPEED;
		int lastInputBufferEndTime = this.inputBuffer.getAgenda().getLastEntry().getEndTime();

		// Handle conflict
		if(startTime < lastInputBufferEndTime){
			this.handleBookInConflict(this.INPUT_BUFFER_FAILURE_ID); 
		}
		
		// Calculate all available conveyor resources
		this.getAvailableConveyorResources(this.inputBuffer);
	}

	
	/**
	 * Selects an output buffer and a lift corresponding to the strategy
	 * 
	 * @throws ResourceException calculate route error
	 */
	private void selectOutputResources() throws ResourceException{
		// Select corresponding lift
		if(this.lift == null){
			// Select the lift which is responsible for the rackr resource
			for (Lift l : this.lifts) {
				if(Math.abs(l.getPosition().getX() - this.rackResource.getPosition().getX()) == 1){
					this.lift = l;
					break;
				}
			}
		}
		
		// Select output buffer
		if(this.outputBuffer == null){
			List<Buffer> outputBuffers = new ArrayList<Buffer>();
			for (Buffer b : this.buffers) {
				if(b.getType() == Buffer.Type.OUT && Math.abs(b.getPosition().getX() - this.lift.getPosition().getX()) == 1){
					outputBuffers.add(b);
				}
			}
			this.outputBuffer = outputBuffers.get(1);
			System.out.println("choosen buffer: " + this.outputBuffer);
		}
		
		AgendaEntry lastLiftAE = this.lift.getAgenda().getLastEntry();
		Position liftStartPosition = lastLiftAE.getEndPos() ;
		// Get initial lift position if position of last agenda entry is null
		if(liftStartPosition == null){
			liftStartPosition = this.lift.getPosition();
		}
		int liftEndPositionY = liftStartPosition.getY();
		int rackResourcePositionY = this.rackResource.getPosition().getY();
		
		int route = this.calculateRouteFromTo(this.lift, this.rackResource);
		int lastLiftEndTime = lastLiftAE.getEndTime();
		int liftEndTime = lastLiftEndTime + route*this.LIFT_SPEED;
		
		// Handle conflict if load has to wait for lift in rack resource
		if(liftEndPositionY != rackResourcePositionY && liftEndTime>this.systemEntryTime){
			this.handleBookOutConflict(this.LIFT_FAILURE_ID);
		}
		
		// Calculate time to move the load on the lift, if the lift is next to the rack resource
		route = this.calculateRouteFromTo(this.rackResource, this.lift);
		int offsetTime = this.systemEntryTime + route*this.CONVEYOR_SPEED;
		System.out.println("load lift (rack to lift): " + offsetTime);
		
		// Calculate time to move the lift to the output buffer
		route = this.calculateRouteFromTo(this.rackResource, this.outputBuffer);
		offsetTime = offsetTime + route*this.LIFT_SPEED;
		System.out.println("move lift (lift to next to output buffer): " + offsetTime);
		
		// Calculate time to unload lift
		route = 1;
		offsetTime = offsetTime + route*this.CONVEYOR_SPEED;
		System.out.println("unload lift (lift to output buffer): " + offsetTime);
		
		int lastBufferEndTime = this.outputBuffer.getAgenda().getLastEntry().getEndTime();
		
		// Handle conflict
		if(offsetTime < lastBufferEndTime){
			this.handleBookOutConflict(this.OUTPUT_BUFFER_FAILURE_ID);
		}
		
		// Calculate time to move the load on the lift, if the lift is next to the rack resource
		offsetTime = this.systemEntryTime + route*this.CONVEYOR_SPEED;
		System.out.println("load lift (rack to lift): " + offsetTime);
		
		// Calculate time to move the lift to the output buffer
		route = this.calculateRouteFromTo(this.rackResource, this.outputBuffer);
		offsetTime = offsetTime + route*this.LIFT_SPEED;
		System.out.println("move lift (lift to next to output buffer): " + offsetTime);
		
		// Calculate time to unload lift
		route = 1;
		offsetTime = offsetTime + route*this.CONVEYOR_SPEED;
		System.out.println("unload lift (lift to output buffer): " + offsetTime);
		this.estimatedConveyorTime = offsetTime;
		
		// Handle conflict if needed
		while(this.hasBookOutConveyorConflict(this.outputBuffer, estimatedConveyorTime)){
			this.handleBookOutConflict(this.CONVEYOR_FAILURE_ID);
		}
		
		// Calculate available conveyor resources
		this.getAvailableConveyorResources(this.outputBuffer);
	}

}
